// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Cloud.Collections;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Actors;
using System.Threading.Tasks;
using System.Diagnostics.Contracts;

namespace ActorStressTests
{
    public static class StressPerf
    {
        public static void ConcurrentListAdd(string fabricAddress, bool useGateway, int numReplicas, int numClients, int numItems, string item)
        {
            Console.WriteLine("Perf -- ConcurrentListAdd({0}x{1}x{3}, {2})", numClients, numItems, item, numReplicas);
            var listName = "ConcurrentList"+numReplicas;
            ListTests.EnsureListCreated(fabricAddress, listName, numReplicas);
            Uri fabricUri = new Uri(@"net.tcp://" + fabricAddress);
            var actorUri = new Uri("fabric:/actor/list/" + listName);
            var runners = new Task[numClients];
            var pushParameters = new object[] {listName, "right", item};

            var outerClient = new FabricActorClient(fabricUri, actorUri, useGateway);

            outerClient.CallMethod("TryConstruct", new object[] { listName });
            outerClient.CallMethod("Clear", new object[] { listName });

            // Initialize all clients here.  Don't want client spin-up to be part of the perf timing.
            // (But we can also report on how long this takes.)
            var sw = Stopwatch.StartNew();
            var clients = new FabricActorClient[numClients];
            for (int i = 0; i < numClients; i++) clients[i] = new FabricActorClient(fabricUri, actorUri, useGateway);
            sw.Stop();

            Console.WriteLine("Time to spin up {0} clients: {1} ms", numClients, sw.ElapsedMilliseconds);

            sw = Stopwatch.StartNew();
            for (int i = 0; i < numClients; i++)
            {
                runners[i] = Task.Factory.StartNew(state =>
                    {
                        var client = state as FabricActorClient;
                        for (int j = 0; j < numItems; j++) client.CallMethod("Push", pushParameters);
                    }, clients[i], TaskCreationOptions.LongRunning);
            }
            Task.WaitAll(runners);
            sw.Stop();

            Console.WriteLine("{0}x{1} writes completed in {2} ms", numClients, numItems, sw.ElapsedMilliseconds);

            // Close down inner clients.
            for (int i = 0; i < numClients; i++) clients[i].Dispose();

            using (outerClient)
            {
                var length = outerClient.CallMethod<int>("Length", new object[] { listName });
                Stress.Assert(length == numClients * numItems,
                    String.Format("Expected length {0}, got length {1}", numClients * numItems, length));
            }
        }


        public static void PerfAddOneMillionItems(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("Perf test - adding one million integers");
            CloudList<int> manyItems = new CloudList<int>(fabricAddress, "One Million integers", useGateway);
            manyItems.Clear();
            const int num = 1000000;
            Stopwatch sw = new Stopwatch();

            for (int numReps = 0; numReps < 4; numReps++)
            {
                manyItems.Clear();
                Console.WriteLine("Adding {0} items via AddRangeAsync in one call (the call chunks data on 64 KB boundaries).", num);
                int[] array = new int[num];
                // Inserting backwards to stress Sort.
                for (int j = 0; j < array.Length; j++)
                    array[j] = num - 1 - j;                
                sw.Restart();
                Task t = manyItems.AddAsync(array);
                t.Wait();
                sw.Stop();

                // To look for silly bugs in Sort, make sure the list indices aren't contiguously going from 0 to num - 1.
                manyItems.RemoveAt(0);
                manyItems.Insert(0, num - 1);

                Console.WriteLine("Adding {0} items via AddRangeAsync took {1} ms", num, sw.ElapsedMilliseconds);

                VerifyListIsCorrect(manyItems, num, true);

                Console.WriteLine("Sorting the list.");
                sw.Restart();
                Task task = manyItems.SortAsync();
				/*
                while (!task.IsCompleted)
                {
                    Console.Write('.');
                    Thread.Sleep(1000);
                }
                */
				task.Wait();
				
                sw.Stop();
                Console.WriteLine("Sorting {0} items took {1} ms", num, sw.ElapsedMilliseconds);

                VerifyListIsCorrect(manyItems, num);

                // Sort a second time, and ensure it is still sorted.
                sw.Restart();
                manyItems.SortAsync().Wait();
                sw.Stop();
                Console.WriteLine("Re-sorting the already sorted list took {0} ms", sw.ElapsedMilliseconds);

                VerifyListIsCorrect(manyItems, num);

                Console.WriteLine("Removing {0} items from the list, by calling RemoveRangeAsync(0, num).", num);
                sw.Restart();
                manyItems.RemoveRangeAsync(0, num).Wait();
                sw.Stop();
                Console.WriteLine("Removing {0} items via RemoveRangeAsync(0) took {1} ms", num, sw.ElapsedMilliseconds);

                Console.WriteLine();
            }

            // Don't leave a lot of data around - try to save some memory (though we've already removed elements above.)
            manyItems.Clear();
            manyItems.Destroy();
            manyItems.Dispose();
        }

        private static void VerifyListIsCorrect(CloudList<int> list, int expectedNumItems, bool reverse = false)
        {
            // Verify results
            int currentCount = list.Count;
            if (currentCount != expectedNumItems)
                throw new Exception(String.Format("Oops, after adding {0} items, Count should have been that number!  Got: {1}", expectedNumItems, currentCount));
            int[] snapshot = list.ToArrayAsync().Result;
            for (int k = 0; k < expectedNumItems; k++)
            {
                int expected = reverse ? expectedNumItems - 1 - k : k;
                if (snapshot[k] != expected)
                    throw new Exception(String.Format("After AddRangeAsync, the returned snapshot array was out of order!  Element at index {0}" + 
                                                      "was {1} in snapshot.  From collection directly: {2}.  Expected: {3}",
                                                      k, snapshot[k], list[k], expected));
            }
        }

        const int NumEventSourceEvents = 200000;

        public static void EventSourcePerfTest(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("Testing performance of EventSource type (think ETW-like events).  Time: {0}", DateTime.Now);
            var actorUri = new Uri("fabric:/actor/adhoc/EventSourcePerf");

            FabricActorClient actor = FabricActorClient.CreateOrConnectToActor(fabricAddress, "fabric:/actor/adhoc", 
                actorUri, "EmptyActorReplica", useGateway);
            actor.DeployAssemblyIfNeeded(typeof(StressPerf).Assembly);

            Console.WriteLine("Firing {0} events...", NumEventSourceEvents);
            var timeMS = actor.CallMethod<long>("FireEventSourceEvents", new Object[] { actorUri.ToString() });
            Console.WriteLine("Fired {0} events in {1} ms.  {2} events / sec", NumEventSourceEvents, timeMS, 1000 * NumEventSourceEvents / timeMS);
            actor.Dispose();
        }

        [ActorMethod]
        public static Object FireEventSourceEvents(IActorState state, Object[] parameters)
        {
            Contract.Requires(state != null);
            Contract.Requires(parameters != null);
            Contract.Requires(parameters.Length == 1);

            String actorName = (string)parameters[0];
            
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i < NumEventSourceEvents; i++)
                ActorETWEventSource.Log.ActorMethodSignatureFailure(actorName, null, "Not an actual assembly name (this perf test is firing this event repeatedly)",
                    "FakeMethodNameForPerfTesting");
            sw.Stop();
            return sw.ElapsedMilliseconds;
        }

    }
}
